<!--
Tracing template
----------------
Test_shell has an optional tracing feature.  It can be enabled by running
with the --enable-tracing option.  Tracing causes events to be dropped into
a trace file during runtime.

This HTML file can be used to render the output from the trace.  You'll need
to rename your trace data to "trace_data.js" in the same directory with this
HTML file, and then you can visualize the trace.

Lots of work remains on this tracing tool; currently development is on hold.

-->

<html>
<head>
<title>
Trace Events
</title>
<style>
body {
  font-family: "Courier New";
  font-size: 9pt;
}

#header {
  position: absolute;
  top: 0px;
  left: 0px;
  border-bottom: 1px dashed black;
  background-color: #F0F0F0;
  z-index: 3;
}

#outer {
  position: relative;
  height: 200px;
}

#time_scale {
  height: 15px;
  width: 100%;
}

#tooltip {
  position: absolute;
  background-color: #FFFFCC;
  display: none;
  font-family: "Courier New";
  font-size: 9pt;
  padding: 5px;
  border: 1px solid #CCCC88;
  z-index: 3;
}

#legend {
  position: fixed;
  left: 10px;
  bottom: 10px;
  padding: 5px;
  border: 1px solid silver;
  z-index: 10;
  background-color: #f0f0f0;
}

h2 {
  margin: 5px;
}

#instructions {
  position: absolute;
  top: 
  float: right;
  display: none;
}

li.time_tick {
  background-color: #FFFFCC;
  height: 15px;
}

li {
  background: pink;
  position: absolute;
  height: 10px;
  list-style: none;
  margin: 0px;
  padding: 0px;
  z-index: 2;
}

li:hover {
  border: 1px solid red;
}

.url {
  background-color: green;
}

.http {
  background-color: blue;
}

.socket {
  background-color: black;
}

.v8 {
  background-color: orange; 
}

.loop {
  background-color: gray; 
}

.io {
  background-color: blue;
}

</style>

<script src='trace_data.js'></script>
<script>
var scale = 100000;
var row_height = 15;
var trace_initial_time = 0;
var trace_threads = {};
var heartbeats = [];
var trace_total_time = 0;

function process_raw_events() {
  trace_initial_time = raw_trace_events[0].usec_begin;
  var stack = [];
  var e;
  for (var i in raw_trace_events) {
    e = raw_trace_events[i];
    var trace_events = trace_threads["e.tid"];
    if (!trace_events) {
      trace_events = [];
      trace_threads["e.tid"] = trace_events;
    }
    if (e.name.indexOf("heartbeat.") == 0) {
      heartbeats.push(e);
    } else if (e.type == "BEGIN") {
      trace_events.push(e);
      stack.unshift(e);
    } else if (e.type == "END") {
      for (var s in stack) {
        var begin = stack[s];
        if ((begin.id == e.id) && (begin.name == e.name) &&
            (begin.pid == e.pid) && (begin.tid == e.tid)) {
          begin.usec_end = e.usec_begin;
          begin.duration = begin.usec_end - begin.usec_begin;
          begin.extra += " " + e.extra;
          stack.splice(s, 1);
          break;
        } 
      }
    } else if (e.type == "INSTANT") {
      trace_events.push(e);
      e.duration = 0;
    }
  }
  if (e.usec_end)
    trace_total_time = e.usec_end - trace_initial_time;
  else
    trace_total_time = e.usec_begin - trace_initial_time;
}

function compute_scale() {
  var outer = document.getElementById("outer");
  scale = Math.floor(trace_total_time / (outer.offsetWidth - (row_height * 2)));
};

function show_details(tid, i, event) {
  var trace_events = trace_threads["e.tid"];
  var inner = trace_events[i].name + " " +
              trace_events[i].duration / 1000 + "ms<br />" + 
              trace_events[i].id  + "<br />" + 
              trace_events[i].extra  + "<br />";
  var tooltip = document.getElementById("tooltip");
  tooltip.innerHTML = inner;
  if (window.event)
    event = window.event;
  tooltip.style.top = event.pageY + 3;
  tooltip.style.left = event.pageX + 3;
  tooltip.style.display = "block";
};

function generate_time_scale() {
  var view_size = window.clientWidth;
  var body_size = document.body.scrollWidth;
  var inner = "";
  
  var step_ms = Math.floor(scale / 10); // ms per 100px
  var pow10 = Math.pow(10, Math.floor(Math.log(step_ms) / Math.log(10)));
  var round = .5 * pow10;
  step_ms = round * (Math.floor(step_ms / round)); // round to a multiple of round
  for (var i = step_ms; i < trace_total_time / 1000; i += step_ms) {
    var x = Math.floor(i * 1000 / scale);
    inner += "<li class='time_tick' style='left: " + x + "px'>" + i + "</li>";
  }
  var time_scale = document.getElementById("time_scale");
  time_scale.innerHTML = inner;
  time_scale.style.width = document.body.scrollWidth;
}

function generate_io_graph(trace_events, top) {
  var max_height = 200;
  var bucket_size = 50000;  // millisecs

  var inner = "";
  var buckets = new Array();
  var value = 0;
  var max_bucket = 0;

  // Go through events and find all read samples.
  // Aggregate data into buckets.
  for (var i in trace_events) {
    var e = trace_events[i];
    if (e.name != "socket.read") {
      continue;
    }

    var bytes = parseInt(e.extra);
//    bytes = 400;

    var start_time = e.usec_begin - trace_initial_time;
    var mybucket = Math.floor(start_time / bucket_size);

    if (buckets[mybucket] == undefined) {
      buckets[mybucket] = 0;
    }
    buckets[mybucket] += bytes;

    if (buckets[max_bucket] == undefined || 
        buckets[max_bucket] < buckets[mybucket]) {
      max_bucket = mybucket;
    }
  }

  for (var index = 0; index < buckets.length; index++) {
    var left = index * Math.floor(bucket_size / scale);
    var width = Math.floor(bucket_size / scale);
    if (width == 0)
      width = 1;

    var height;
    if (buckets[index] == undefined) {
      height = 0;
    } else {
      height = (buckets[index] / buckets[max_bucket]) * max_height;
    }

    var my_top = max_height - height;

    var style = "top: " + my_top + "px; left: " + left + "px; width: " + width + "px; height:" + height + "px;";
    var cls = "io";
    inner += "<li title='" + buckets[index] + " bytes' class='" + cls + "' id='li-" + i + "' style='" + style + "'></li>\n";
  }

  var subchart = document.createElement('div');
  subchart.setAttribute("class", "iograph");
  subchart.setAttribute("id", trace_events[0].tid);
  subchart.innerHTML = inner;
  subchart.style.height = max_height;
  subchart.style.top = top;
  subchart.style.left = 0;
  subchart.style.position = "absolute";
//  subchart.style.width = row_height + last_max_x;
  var chart = document.getElementById("chart");
  chart.appendChild(subchart);

  return top + max_height;
}

function generate_subchart(trace_events, top) {
  var start_top = top;
  var heights = new Array();
  var max_row = 0;
  var inner = "";
  var last_max_time = 0;
  var last_max_x = 0;
  for (var i in trace_events) {
    var e = trace_events[i];
    var start_time = e.usec_begin - trace_initial_time;
    var left = row_height + Math.floor(start_time / scale);
    var width = Math.floor(e.duration / scale);
    if (width == 0)
      width = 1;

    if (heights[e.id]) {
      top = heights[e.id];
    } else {
      max_row += row_height;
      heights[e.id] = max_row;
      top = heights[e.id];
    }
    //if (start_time < last_max_time)
    //  top += row_height;
    var style = "top: " + top + "px; left: " + left + "px; width: " + width + "px;";
    var js = 'javascript:show_details("' + e.tid + '", ' + i + ', event);';
    var cls = e.name.split('.')[0];
    inner += "<li class='" + cls + "' onmouseover='" + js + "' id='li-" + i + "' style='" + style + "'></li>\n";
    last_max_time = start_time + e.duration;
    last_max_x = left + width;
  }
  var subchart = document.createElement('div');
  subchart.setAttribute("class", "subchart");
  subchart.setAttribute("id", trace_events[0].tid);
  subchart.innerHTML = inner;
  subchart.style.top = start_top + "px";
  subchart.style.height = top + row_height;
  subchart.style.width = row_height + last_max_x;
  subchart.style.position = "absolute";
  var chart = document.getElementById("chart");
  chart.appendChild(subchart);
  
  return top;
};

function generate_chart() {
  var chart = document.getElementById("chart");
  chart.innerHTML = "";
  var top = 60;
  for (var t in trace_threads) {
    top = generate_io_graph(trace_threads[t], top);
    top = generate_subchart(trace_threads[t], top);
  }
  generate_time_scale();
}

function change_scale(event) {
  if (!event)
    event = window.event;
  if (!event.shiftKey)
    return;
  var delta = 0;
  if (event.wheelDelta) {
    delta = event.wheelDelta / 120;
  } else if (event.detail) {
    delta = - event.detail / 3;
  }
  if (delta) {
    var tooltip = document.getElementById("tooltip");
    tooltip.style.display = "none";
    var factor = 1.1;
    if (delta < 0)
      scale = Math.floor(scale * factor);
    else
      scale = Math.floor(scale / factor);
    if (scale > 300000)
      scale = 300000;
    generate_chart();
    if (event.preventDefault)
      event.preventDefault();
  }
  event.returnValue = false;
};

function initial_load() {
  if (window.addEventListener)
    window.addEventListener('DOMMouseScroll', change_scale, false);
  window.onmousewheel = document.onmousewheel = change_scale;

  process_raw_events();
  compute_scale();
  generate_chart();
};

</script>
</head>
<body onload='initial_load();'>
<div id="header">
<h2>Trace Events</h2>
<div id="instructions">
Use shift+mouse-wheel to zoom in and out.
</div>
<div id="time_scale"></div>
</div>
<div id="legend">
<span class="url">&nbsp;</span> URL<br />
<span class="http">&nbsp;</span> HTTP<br />
<span class="socket">&nbsp;</span> Socket<br />
<span class="v8">&nbsp;</span> V8<br />
<span class="loop">&nbsp;</span> TASKS<br />
</div>
<div id="chart">
<div id="outer">
</div>
</div>
<div id="tooltip" ondblclick="this.style.display = 'none';"></div>
</body>
</html>
